using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace nToolbox.Security
{
    /// <summary>
    /// A generic helper class that is created with a defined SymmetricAlgorithm.  It also ensures that the
    /// Key and IV arrays are of the proper size for the desired algorithm, resizing them as necessary.
    /// </summary>
    /// <typeparam name="T">The type of the SymmetricAlgorithm to use.</typeparam>
    public sealed class SymmetricCryptography<T> where T : SymmetricAlgorithm, new()
    {
        private readonly SymmetricAlgorithm algorithm;

        /// <summary>
        /// Constructor.
        /// </summary>
        public SymmetricCryptography( byte[] key, byte[] iv )
        {
            algorithm = new T();
            byte[] newArray = ResizeArray( key, algorithm.KeySize / 8 );
            algorithm.Key = newArray;
            newArray = ResizeArray( iv, algorithm.BlockSize / 8 );
            algorithm.IV = newArray;
        }

        /// <summary>
        /// Constructor using Strings for the key and iv.
        /// </summary>
        public SymmetricCryptography( string key, string iv )
            : this( UnicodeEncoding.Unicode.GetBytes( key ), UnicodeEncoding.Unicode.GetBytes( iv ) )
        {
        }

        /// <summary>
        /// Constructor using a specified algorithm.
        /// </summary>
        public SymmetricCryptography( T algorithm )
        {
            this.algorithm = algorithm;
        }

        /// <summary>
        ///     Encrypts the specified string and returns the resulting string, 
        ///     which can be decrypted back to the original using the <c>Decrypt</c> method.
        /// </summary>
        /// <param name="clearText">The string to be encrypted</param>
        /// <returns>The resulting string from encrypting <c>clearText</c></returns>
        public string Encrypt( string clearText )
        {
            return SymmetricCryptography.Encrypt( algorithm, clearText );
        }

        /// <summary>
        ///     Decrypts the specified string that was encrypted using the <c>Encrypt</c> method, 
        ///     and returns the resulting string.
        /// </summary>
        /// <param name="encryptedText">The string to be decrypted</param>
        /// <returns>The resulting string from decrypting <c>encryptedText</c></returns>
        public string Decrypt( string encryptedText )
        {
            return SymmetricCryptography.Decrypt( algorithm, encryptedText );
        }

        /// <summary>
        /// Encrypt a string to a stream.
        /// </summary>
        public void EncryptToStream( string clearText, Stream outputStream )
        {
            SymmetricCryptography.EncryptToStream( algorithm, clearText, outputStream );
        }

        /// <summary>
        /// Decrypt a string out of a stream.  Intended to be used on streams that were originally encrypted
        /// with EncryptToStream using the same algorithm.
        /// </summary>
        public string DecryptFromStream( Stream inputStream )
        {
            return SymmetricCryptography.DecryptFromStream( algorithm, inputStream );
        }

        /// <summary>
        ///     Encrypts the specified Stream and returns the resulting Stream, 
        ///     which can be decrypted back to the original using the <c>Decrypt</c> method.
        /// </summary>
        /// <param name="clearStream">The Stream to be encrypted</param>
        /// <returns>The resulting Stream from encrypting <c>clearStream</c></returns>
        public void Encrypt( Stream clearStream, Stream outputStream )
        {
            SymmetricCryptography.Encrypt( algorithm, clearStream, outputStream );
        }

        /// <summary>
        ///     Decrypts the specified Stream that was encrypted using the <c>Encrypt</c> method, 
        ///     and returns the resulting Stream.
        /// </summary>
        /// <param name="encryptedStream">The Stream to be decrypted</param>
        /// <returns>The resulting Stream from decrypting <c>encryptedStream</c></returns>
        public void Decrypt( Stream encryptedStream, Stream outputStream )
        {
            SymmetricCryptography.Decrypt( algorithm, encryptedStream, outputStream );
        }

        /*  These methods don't work, although it looks like they should.  Will work on later.
        public byte[] Encrypt( byte[] input )
        {
            return SymmetricCryptography.Encrypt( algorithm, input );
        }

        public byte[] Decrypt( byte[] input )
        {
            return SymmetricCryptography.Decrypt( algorithm, input );
        }
        */
        private static byte[] ResizeArray( byte[] array, int size )
        {
            if ( array.Length < size )
            {
                byte[] newArray = new byte[size];
                for ( int i = 0; i < size; i++ )
                {
                    newArray[i] = array[i % array.Length];
                }
                return newArray;
            }
            else if ( array.Length > size )
            {
                Array.Resize( ref array, size );
            }
            return array;
        }

    }
}
